﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Globalization;

namespace Notepad_Clone
{
    class PlainTextDocumentPaginator : DocumentPaginator
    {
        char[] charsBreak = new char[] { ' ', '-' };
        string txt = string.Empty;
        string txtHeader = null;
        Typeface face = new Typeface(string.Empty);
        double em = 11;
        Size sizePage = new Size(8.5, 11);
        Size sizeMax = new Size(0, 0);
        Thickness margins = new Thickness(1);
        PrintTicket prntkt = new PrintTicket();
        TextWrapping txtwrap = TextWrapping.Wrap;

        List<DocumentPage> listPages;

        public string Text
        {
            set { this.txt = value; }
            get { return this.txt; }
        }

        public TextWrapping TextWrapping
        {
            set { this.txtwrap = value; }
            get { return this.txtwrap; }
        }

        public Thickness Margins
        {
            set { this.margins = value; }
            get { return this.margins; }
        }

        public Typeface Typeface
        {
            set { this.face = value; }
            get { return this.face; }
        }

        public double FaceSize
        {
            set { this.em = value; }
            get { return this.em; }
        }

        public PrintTicket PrintTicket
        {
            get { return this.prntkt; }
            set { this.prntkt = value; }
        }

        public string Header
        {
            set { this.txtHeader = value; }
            get { return this.txtHeader; }
        }

        public override bool IsPageCountValid
        {
            get
            {
                if (this.listPages == null)
                {
                    this.Format();
                }

                return true;
            }
        }

        public override int PageCount
        {
            get
            {
                if (this.listPages == null)
                {
                    return 0;
                }

                return this.listPages.Count;
            }
        }

        public override Size PageSize
        {
            get
            {
                return this.sizePage;
            }

            set
            {
                this.sizePage = value;
            }
        }

        public override IDocumentPaginatorSource Source
        {
            get
            {
                return null;
            }
        }

        public override DocumentPage GetPage(int pageNumber)
        {
            return this.listPages[pageNumber];
        }

        private FormattedText GetFormattedText(string str)
        {
            return new FormattedText(str, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, this.face, this.em, Brushes.Black);
        }

        private void Format()
        {
            List<PrintLine> listLines = new List<PrintLine>();

            FormattedText formtxtSample = this.GetFormattedText("W");

            double width = this.PageSize.Width - this.Margins.Left - this.Margins.Right;

            if (width < formtxtSample.Width)
            {
                return;
            }

            string strLine;
            Pen pn = new Pen(Brushes.Black, 2);
            StringReader reader = new StringReader(txt);

            while (null != (strLine = reader.ReadLine()))
            {
                this.ProcessLine(strLine, width, listLines);
            }

            reader.Close();

            double heghtLine = formtxtSample.LineHeight + formtxtSample.Height;
            double heght = this.PageSize.Height - this.Margins.Top - this.Margins.Bottom;
            int linesPerPage = (int)(heght / heghtLine);

            if (linesPerPage < 1)
            {
                return;
            }

            int numPages = (listLines.Count + linesPerPage - 1) / linesPerPage;
            double xStart = this.Margins.Left;
            double yStart = this.Margins.Top;

            this.listPages = new List<DocumentPage>();
            for (int iPage = 0, iLine = 0; iPage < numPages; iPage++)
            {
                DrawingVisual vis = new DrawingVisual();
                DrawingContext dc = vis.RenderOpen();

                if (this.Header != null && this.Header.Length > 0)
                {
                    FormattedText formtxt = this.GetFormattedText(this.Header);
                    formtxt.SetFontWeight(FontWeights.Bold);
                    Point ptText = new Point(xStart, yStart - 2 * formtxt.Height);
                    dc.DrawText(formtxt, ptText);
                }

                if (numPages > 1)
                {
                    FormattedText formtxt = this.GetFormattedText("Page " + (iPage + 1) + " of " + numPages);
                    formtxt.SetFontWeight(FontWeights.Bold);
                    Point ptText = new Point(
                        (this.PageSize.Width + this.Margins.Left - this.Margins.Right - formtxt.Width) / 2,
                        this.PageSize.Height - this.Margins.Bottom + formtxt.Height);
                    dc.DrawText(formtxt, ptText);
                }

                for (int i = 0; i < linesPerPage; i++, iLine++)
                {
                    if (iLine == listLines.Count)
                    {
                        break;
                    }

                    string str = listLines[iLine].String;
                    FormattedText formtxt = this.GetFormattedText(str);
                    Point ptText = new Point(xStart, yStart + i * heghtLine);
                    dc.DrawText(formtxt, ptText);

                    if (listLines[iLine].Flag)
                    {
                        double x = xStart + width + 6;
                        double y = yStart + i * heghtLine + formtxt.Baseline;
                        double len = this.face.CapsHeight * this.em;
                        dc.DrawLine(pn, new Point(x, y), new Point(x + len, y - len));
                        dc.DrawLine(pn, new Point(x, y), new Point(x, y - len / 2));
                        dc.DrawLine(pn, new Point(x, y), new Point(x + len / 2, y));
                    }
                }

                dc.Close();

                DocumentPage page = new DocumentPage(vis);
                listPages.Add(page);
            }

            reader.Close();
        }

        void ProcessLine(string str, double width, List<PrintLine> list)
        {
            str = str.TrimEnd(' ');

            if (this.TextWrapping == TextWrapping.NoWrap)
            {
                do
                {
                    int length = str.Length;
                    while (this.GetFormattedText(str.Substring(0, length)).Width > width)
                    {
                        length--;
                    }

                    list.Add(new PrintLine(str.Substring(0, length), length < str.Length));
                    str = str.Substring(length);
                } while (str.Length > 0);
            }
            else
            {
                do
                {
                    int length = str.Length;
                    bool flag = false;

                    // 文字宽度超出范围
                    while (this.GetFormattedText(str.Substring(0, length)).Width > width)
                    {
                        int index = str.LastIndexOfAny(this.charsBreak, length - 2);
                        
                        // 本行末尾是否包含空格或者"-"
                        if (index != -1)
                        {
                            length = index + 1;
                        }
                        else
                        {
                            index = str.IndexOfAny(this.charsBreak);
                            if (index != -1)
                            {
                                length = index + 1;
                            }

                            // 设置为TextWrapping.Wrap，在行为添加箭头标志
                            if (this.TextWrapping == TextWrapping.Wrap)
                            {
                                while (this.GetFormattedText(str.Substring(0, length)).Width > width)
                                {
                                    length--;
                                }

                                flag = true;
                            }

                            break;
                        }
                    }

                    list.Add(new PrintLine(str.Substring(0, length), flag));
                    str = str.Substring(length);
                } while (str.Length > 0);
            }
        }

        class PrintLine
        {
            public string String;
            public bool Flag;
            public PrintLine(string str, bool flag)
            {
                this.String = str;
                this.Flag = flag;
            }
        }
    }
}
